/*
 * ModelCC, distributed under ModelCC Shared Software License, www.modelcc.org
 */

package org.modelcc.metamodel;

import java.util.Collections;
import java.util.Map;
import java.util.Set;

/**
 * ModelCC metamodel
 * 
 * @author Fernando Berzal (berzal@acm.org)
 */
public abstract class Model<E extends ModelElement> implements java.io.Serializable
{
    /**
     * Set of elements of this model.
     */
    private Set<E> elements;

    /**
     * ModelElement subclasses map.
     */
    private Map<E,Set<E>> subelements;

    /**
     * ModelElement superclass map.
     */
    private Map<E,E> superelements;
    
    /**
     * Class to element map.
     */
    private Map<Class,E> classToElement;

    
    /**
     * Constructor.
     * @param elements the set of elements of this model.
     * @param subelements the element subclass map
     * @param superelements the element superclass map
     * @param classToElement the class to element map
     */
    public Model(
    		Set<E> elements,
    		Map<E,Set<E>> subelements,
    		Map<E,E> superelements,
    		Map<Class,E> classToElement) 
    {
        this.elements = elements;
        this.subelements = subelements;
        this.superelements = superelements;
        this.classToElement = classToElement;
    }
    
    /**
     * @return the elements of this model
     */
    public Set<E> getElements() {
        return Collections.unmodifiableSet(elements);
    }

    /**
     * @return the element subelements map
     */
    public Map<E, Set<E>> getSubelements() {
        return Collections.unmodifiableMap(subelements);
    }

    /**
     * @return the element superelements map
     */
    public Map<E,E> getSuperelements() {
        return Collections.unmodifiableMap(superelements);
    }

    /**
     * @return the classToElement
     */
    public Map<Class, E> getClassToElement() {
        return Collections.unmodifiableMap(classToElement);
    }


    /**
     * Find element by name
     * @param name element name
     * @return Model element
     * @throws ModelException
     */
    public final E findElement (String name) throws ModelException 
    {
		E me = null;
		
		for (E mec: getElements()) {
			if (mec.getElementClass().getCanonicalName().endsWith(name) && name.endsWith(mec.getElementClass().getSimpleName())) {
				if (me == null)
					me = mec;
				else
					throw new ModelException("Ambiguous element definition???");
			}
		}
		if (me != null)
	    	return me;
		
		for (E mec: getElements()) {
			if (mec.getElementClass().getName().endsWith(name) && name.endsWith(mec.getElementClass().getSimpleName())) {
				if (me == null)
					me = mec;
				else
					throw new ModelException("Ambiguous element definition???");
			}
		}
		if (me != null)
	    	return me;

		for (E mec: getElements()) {
			if (mec.getElementClass().getSimpleName().endsWith(name) && name.endsWith(mec.getElementClass().getSimpleName())) {
				if (me == null)
					me = mec;
				else
					throw new ModelException("Ambiguous element definition???");
			}
		}
		if (me != null)
	    	return me;

		return null;
    }
    
    
    // toString
    
    public String toString ()
    {
    	return "MODEL "+elements.toString();
    }
}
